﻿using System;
using System.Collections.Generic;
using System.Linq;
using ClosedXML.Excel;
using MFilesAPI;

namespace FSAG.UserImporter
{
    /// <summary>
    /// Provides methods and properties for mfiles issues regarding groups
    /// </summary>
    public static class GroupManager
    {
        private static MFilesServerApplication MFilesServerApplication { get; set; }
        private static Vault AcademiesVault { get; set; }
        private static string Path { get; set; }
        private static List<UserGroup> ModifiedGroups { get; set; }
        private static List<UserGroup> GroupsToDelete { get; set; }
        private static List<UserGroup> DeletedGroups { get; set; }
        private static List<UserGroup> UpdatedGroups { get; set; }

        /// <summary>
        /// Initialize all necessary variables, sould be called as first method
        /// </summary>
        /// <param name="mFilesServerApplication">Basic MFiles Server Application instance</param>
        /// <param name="vault">Instance to the vault object</param>
        /// <param name="path">Location path to the excel user list</param>
        public static void Initialize(MFilesServerApplication mFilesServerApplication, Vault vault, string path)
        {
            MFilesServerApplication = mFilesServerApplication;
            AcademiesVault = vault;
            Path = path;
            ModifiedGroups = new List<UserGroup>();
            GroupsToDelete = new List<UserGroup>();
            DeletedGroups = new List<UserGroup>();
            UpdatedGroups = new List<UserGroup>();
        }

        /// <summary>
        /// Used to import respectively update/create the groups based on the excel list
        /// </summary>
        /// <param name="mFilesServerApplication">Basic MFiles Server Application instance</param>
        /// <param name="vault">Instance to the vault object</param>
        /// <param name="path">Location path to the excel user list</param>
        /// <returns>Returns true, if the import was successful</returns>
        public static bool StartImportGroups(MFilesServerApplication mFilesServerApplication, Vault vault, string path)
        {
            Initialize(mFilesServerApplication, vault, path);

            if (AcademiesVault == null || vault == null)
                return false;
            if (LoadGroups())
            {
                CreateGroups();
                DeleteAllDefinedGroups();
                return true;
            }
            return false;
        }

        /// <summary>
        /// Used to update the excel list after the import procedure
        /// </summary>
        /// <param name="workbook">Workbook instance for updating values</param>
        public static void FinalizeGroupImport(XLWorkbook workbook)
        {
            if (workbook==null) return;

            ConfigManager.Log.Info("Gruppenliste wird aktualisiert..");
            IXLWorksheet worksheet = workbook.Worksheet(2);
            IXLRange rangeGroupNames = worksheet.Range(ConfigManager.GetSetting("RangeGroupNames"));

            UpdateAccountInformation(rangeGroupNames, worksheet, UpdatedGroups);
            UpdateAccountInformation(rangeGroupNames, worksheet, DeletedGroups, true);
            DeleteUnusedGroups(rangeGroupNames,worksheet);
            ConfigManager.Log.Info("Gruppenliste wurde aktualisiert (modified gelöscht, IDs aktualisiert)");
        }

        /// <summary>
        /// This Method is used to remove all old obsolete group entries which contains a not existing id and no group name
        /// </summary>
        /// <param name="rangeGroupNames">Excel Range for all full names</param>
        /// <param name="worksheet">Worksheet instance of the user list</param>
        private static void DeleteUnusedGroups(IXLRangeBase rangeGroupNames, IXLWorksheet worksheet)
        {
            foreach (IXLCell groupNameCell in rangeGroupNames.Cells(cell => cell.WorksheetRow().RowNumber() < ConfigManager.GetSettingAsNumber("LastUsedGroupRow")))
            {
                int groupIDNumber;
                var groupID =
                    worksheet.Cell(groupNameCell.WorksheetRow().RowNumber(), ConfigManager.GetSettingAsNumber("ColumnGroupIDNumber")).Value.ToString().Trim();

                var result = Int32.TryParse(groupID, out groupIDNumber);
                if (!result || !GroupExistsOnServer(groupIDNumber))
                {
                    if (string.IsNullOrEmpty(groupNameCell.Value.ToString()))
                    {
                        worksheet.Range(groupNameCell.WorksheetRow().RowNumber(), ConfigManager.GetSettingAsNumber("FirstUsedColumn"),
                                        groupNameCell.WorksheetRow().RowNumber(), ConfigManager.GetSettingAsNumber("LastUsedColumn"))
                                 .Clear(XLClearOptions.Contents);
                    }
                }
            }
        }

        /// <summary>
        /// Combined method for updating some group data entries depending on the parameter
        /// </summary>
        /// <param name="rangeGroupNames">Excel range with all group names</param>
        /// <param name="worksheet">Worksheet instance with group list</param>
        /// <param name="userGroups">List of groups used for updates</param>
        /// <param name="deleted">If set, the id will be reset (set to 0)</param>
        private static void UpdateAccountInformation(IXLRangeBase rangeGroupNames, IXLWorksheet worksheet,
                                                     IEnumerable<UserGroup> userGroups, bool deleted = false)
        {
            int groupID = 0;
            foreach (UserGroup userGroup in userGroups)
            {
                foreach (IXLCell groupCell in rangeGroupNames.CellsUsed())
                {
                    if (userGroup.Name.Equals(groupCell.Value))
                    {
                        if (!deleted) groupID = userGroup.ID;
                        worksheet.Cell(groupCell.WorksheetRow().RowNumber(), ConfigManager.GetSettingAsNumber("ColumnGroupIDNumber")).Value=groupID;
                        worksheet.Cell(groupCell.WorksheetRow().RowNumber(), ConfigManager.GetSettingAsNumber("ColumnModifiedGroup")).Value= string.Empty;
                        worksheet.Cell(groupCell.WorksheetRow().RowNumber(),ConfigManager.GetSettingAsNumber("ColumnUpdatedOnServerGroup")).Value = DateTime.Now;
                        break;
                    }
                }
                DeleteGroupFromAssignedGroups(userGroup, worksheet.Workbook);
            }
        }

        /// <summary>
        /// Method used to delete a given userGroup from the assigned groups
        /// </summary>
        /// <param name="userGroup">Group to delete from assigned groups</param>
        /// <param name="workbook">Workbook instance for updating values</param>
        private static void DeleteGroupFromAssignedGroups(UserGroup userGroup, XLWorkbook workbook)
        {
            if (userGroup == null) return;

            IXLRange rngAssignedGroups = workbook.Worksheet(1).Range(ConfigManager.GetSetting("RangeAssignedGroups"));
            if ((string.IsNullOrEmpty(userGroup.Name)) && userGroup.ID > 0)
            {
                try
                {
                    userGroup = AcademiesVault.UserGroupOperations.GetUserGroup(userGroup.ID);
                }
                catch (Exception)
                {
                    return;
                }
            }
            rngAssignedGroups.CellsUsed(cell => cell.Value.ToString().Equals(userGroup.Name)).Clear(XLClearOptions.Contents);
            ConfigManager.Log.DebugFormat("Gruppe {0} wurde aus zugewiesenen Gruppen entfernt", userGroup.Name);
        }

        /// <summary>
        /// Method used to delete all groups, which are marked as delete
        /// </summary>
        private static void DeleteAllDefinedGroups()
        {
            foreach (UserGroup deletedGroup in GroupsToDelete)
            {
                if (!GroupExistsOnServer(deletedGroup.ID))
                {
                    ConfigManager.Log.DebugFormat("Zu löschende Gruppe {0} bereits gelöscht", deletedGroup);
                    DeletedGroups.Add(deletedGroup);
                    continue;
                }

                UserGroup userGroup;
                try
                {
                    userGroup = AcademiesVault.UserGroupOperations.GetUserGroup(deletedGroup.ID);
                    AcademiesVault.UserGroupOperations.RemoveUserGroupAdmin(deletedGroup.ID);
                    ConfigManager.Log.InfoFormat("* Gruppe {0} wurde gelöscht", deletedGroup);
                }
                catch (Exception)
                {
                    var groupName = string.IsNullOrEmpty(deletedGroup.Name)?deletedGroup.Name:"Gruppe";
                        ExcelStatusManager.AddStatusMessage(
                            string.Format("{0} konnte nicht gelöscht werden, da bereits gelöscht!", groupName),
                            StatusType.Exception);
                    continue;
                }

                if (userGroup != null && !string.IsNullOrEmpty(userGroup.Name))
                {
                    deletedGroup.Name = userGroup.Name;
                    DeletedGroups.Add(deletedGroup);
                }
            }
        }

        /// <summary>
        /// Creates or Modifies the mfiles groups based on the imported groups
        /// </summary>
        private static void CreateGroups()
        {
            VaultUserGroupOperations userGroupManager = AcademiesVault.UserGroupOperations;
            foreach (UserGroup modifiedGroup in ModifiedGroups)
            {
                UserGroupAdmin updatedUserGroup;
                var userGroupAdmin = new UserGroupAdminClass {UserGroup = modifiedGroup};

                if (GroupExistsOnServer(modifiedGroup))
                {
                    try
                    {
                        modifiedGroup.ID = GetGroupID(modifiedGroup);
                        userGroupManager.UpdateUserGroupAdmin(userGroupAdmin);
                        updatedUserGroup = userGroupAdmin;
                        ConfigManager.Log.InfoFormat("* Gruppe {0} wurde aktualisiert", modifiedGroup.Name);
                    }
                    catch (Exception)
                    {
                        ConfigManager.Log.WarnFormat("Gruppe {0} konnte nicht aktualisiert werden, da nicht vorhanden", modifiedGroup.Name);
                        ExcelStatusManager.AddStatusMessage(
                            string.Format("{0} konnte nicht aktualisiert werden, da nicht vorhanden!", modifiedGroup.Name),
                            StatusType.Exception);
                        continue;
                    }
                }
                else
                {
                    try
                    {
                        updatedUserGroup = userGroupManager.AddUserGroupAdmin(userGroupAdmin);
                        ConfigManager.Log.InfoFormat("* Gruppe {0} wurde erstellt", modifiedGroup.Name);
                    }
                    catch (Exception)
                    {
                        ConfigManager.Log.WarnFormat("Gruppe {0} konnte nicht aktualisiert werden, da bereits vorhanden", modifiedGroup.Name);
                        ExcelStatusManager.AddStatusMessage(
                            string.Format("{0} konnte nicht aktualisiert werden, da bereits vorhanden!", modifiedGroup.Name),
                            StatusType.Exception);
                        continue;
                    }
                }

                if (updatedUserGroup.UserGroup.ID == 0) continue;
                UpdatedGroups.Add(updatedUserGroup.UserGroup);
            }
        }

        /// <summary>
        /// Enquery the modified groups of the excel file defined with the path variable and convert it to the related group objects
        /// </summary>
        /// <returns></returns>
        private static bool LoadGroups()
        {
            IQueryable<Group> groups =
                from c in
                    ExcelQueryInitializer.Group.WorksheetRange<Group>(ConfigManager.GetSetting("FirstUsedCellGroups"),
                                                                      ConfigManager.GetSetting("LastUsedCellGroups"),
                                                                      ConfigManager.GetSetting("WorkSheetNameGroups"))
                select c;

            var groupToIrrevocableDelete = groups.ToList().FindAll(group => group.IsIrrevocableDeleted());
            var modifiedGroups =
                groups.ToList().FindAll(group => group.IsModified());

            if (modifiedGroups.Count == 0 && groupToIrrevocableDelete.Count == 0) return false;

            ConfigManager.Log.DebugFormat("{0} geänderte Gruppen gefunden", modifiedGroups.Count);
            var groupsToDelete = modifiedGroups.ToList().FindAll(group => group.IsDeleted() || (group.IsIrrevocableDeleted() && group.GroupID!=0));
            groupsToDelete = groupsToDelete.Concat(groups.ToList().FindAll(group => group.IsIrrevocableDeleted() && group.GroupID != 0)).ToList();
            ConfigManager.Log.DebugFormat("{0} zu löschende Gruppen gefunden", groupsToDelete.Count);

            modifiedGroups = modifiedGroups.Except(groupsToDelete).ToList();
            groupsToDelete.ForEach(CreateDeletedGroupList);
            modifiedGroups.ForEach(CreateModifiedGroupList);

            if (modifiedGroups.Count > 0) ConfigManager.Log.Info("Geänderte Gruppen wurden erkannt");
            ConfigManager.Log.Info("Gruppen werden aktualisiert/erstellt");
            return true;
        }

        /// <summary>
        /// Helper method for converting a given group into a UserGroup instance and
        /// adding them to the ModifiedGroups list
        /// </summary>
        /// <param name="group">The imported group</param>
        private static void CreateModifiedGroupList(Group group)
        {
            var userGroup = (UserGroupClass) group;
            ModifiedGroups.Add(userGroup);
        }

        /// <summary>
        /// Helper method for converting a given group into a UserGroup instance and
        /// adding them to the GroupsToDelete list
        /// </summary>
        /// <param name="group"></param>
        private static void CreateDeletedGroupList(Group group)
        {
            var userGroup = (UserGroupClass) group;
            GroupsToDelete.Add(userGroup);
        }

        /// <summary>
        /// Method checks wheter a given group exists on the server
        /// </summary>
        /// <param name="userGroup">The group to prove existence on server</param>
        /// <returns>True, if the group exists</returns>
        private static bool GroupExistsOnServer(UserGroup userGroup)
        {
            return AcademiesVault.UserGroupOperations.GetUserGroups().Cast<UserGroup>().Any(@group => @group.Name == userGroup.Name);
        }

        /// <summary>
        /// Helper Method for retrieving the id of a given group
        /// </summary>
        /// <param name="userGroup">Group to look for the id</param>
        /// <returns>The id of a given group or 0 in case of a not existing group</returns>
        private static int GetGroupID(UserGroup userGroup)
        {
            return (from UserGroup @group in AcademiesVault.UserGroupOperations.GetUserGroups() where @group.Name == userGroup.Name select @group.ID).FirstOrDefault();
        }

        /// <summary>
        /// Method checks wheter a group with a given id exists on the server
        /// </summary>
        /// <param name="id">The id of a group</param>
        /// <param name="group">Group instance to prove existence</param>
        /// <returns>True, if the group exists</returns>
        private static bool GroupExistsOnServer(int id = 0,Group group = null)
        {
            try
            {
                if (id == 0 && group != null)
                {
                    id = group.GroupID;
                    
                }
                AcademiesVault.UserGroupOperations.GetUserGroup(id);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }
    }
}